using BepuUtilities;
using System.Runtime.CompilerServices;
using static BepuUtilities.GatherScatter;
#if MYCODE
using BepuUtilities.Vectors;
#else
using System.Numerics;
#endif
namespace BepuPhysics.CollisionDetection.CollisionTasks
{
    public struct ManifoldCandidateScalar
    {
        public float X;
        public float Y;
        public int FeatureId;
    }
    public struct ManifoldCandidate
    {
        public Vector<float> X;
        public Vector<float> Y;
        public Vector<float> Depth;
        public Vector<int> FeatureId;
    }
    public static class ManifoldCandidateHelper
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void CreateInactiveMask(int pairCount, out Vector<int> inactiveLanes)
        {
            inactiveLanes = Vector<int>.Zero;
            ref var laneMasks = ref Unsafe.As<Vector<int>, int>(ref inactiveLanes);
            for (int i = Vector<int>.Count - 1; i >= pairCount; --i)
            {
                Unsafe.Add(ref laneMasks, i) = -1;
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void AddCandidate(ref ManifoldCandidate candidates, ref Vector<int> count, in ManifoldCandidate candidate, in Vector<int> newContactExists, int pairCount)
        {
            // 遗憾的是,增量维护列表非常不适合广泛的矢量化。
            // 每对联系人都有自己的计数,因此在列表中存储新联系人的目标存储位置是不同的。
            // 如果我们有有效的散射器,这将类似于：
            // Stride=Unsafe.SizeOf<Candidate>()/Unsafe.SizeOf<Float>();
            // laneIndices=new Vector{0,1,2,3.}
            // targetIndices=Count*Stride+laneIndices;
            // Scatter(引用候选项.X,引用不安全.As<Vector<Float>,Float>(引用候选项[0].X),计数)
            // 散布(Ref Candidate.Y,Ref Unsafe.As<Vector<Float>,Float>(Ref Candidate[0].Y),Count)
            // Scatter(引用候选项.FeatureId,引用不安全.As<Vector<int>,Float>(引用候选项[0].FeatureId),计数)
            // 但是我们现在没有散货。我们有两个选择：
            // 1)维护矢量化流水线,尽可能用标量运算模拟散点。遇到未定义行为或编译器问题的一些风险。
            // 2)立即降至全标量模式,并输出本次测试的AOS ContactManifolds数组。
            // 请注意,无论如何,我们在完成矢量化对的每个束之后执行概念分散,因此这不会是灾难性的-
            // 我们确实从矢量化中获得了上述所有数学知识的一些好处。
            // 我的猜测是,总体来说,#2有一点优势,但是它需要在API级别做更多的工作--我们需要一个可以处理AOS输出的ExecuteBatch版本。
            // 现在,我们将从#1开始。尽可能用标量代码模拟散射体,并维护矢量化的API。

            for (int i = 0; i < pairCount; ++i)
            {
                if (newContactExists[i] < 0)
                {
                    var targetIndex = count[i];
                    ref var target = ref GetOffsetInstance(ref Unsafe.Add(ref candidates, targetIndex), i);
                    // TODO：检查codegen。如果编译器插入边界检查,则可能值得对源数据执行另一个偏移实例。
                    //GetFirst(ref target.X) = candidate.X[i];
                    //GetFirst(ref target.Y) = candidate.Y[i];
                    //GetFirst(ref target.FeatureId) = candidate.FeatureId[i];
                    SetFirst(ref target.X, candidate.X[i]);
                    SetFirst(ref target.Y, candidate.Y[i]);
                    SetFirst(ref target.FeatureId, candidate.FeatureId[i]);
                }
            }
            count = Vector.ConditionalSelect(newContactExists, count + Vector<int>.One, count);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static void ConditionalSelect(in Vector<int> useA, in ManifoldCandidate a, in ManifoldCandidate b, out ManifoldCandidate result)
        {
            result.X = Vector.ConditionalSelect(useA, a.X, b.X);
            result.Y = Vector.ConditionalSelect(useA, a.Y, b.Y);
            result.Depth = Vector.ConditionalSelect(useA, a.Depth, b.Depth);
            result.FeatureId = Vector.ConditionalSelect(useA, a.FeatureId, b.FeatureId);
        }

        static Vector<int> left;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static void CandidateExists(in ManifoldCandidate candidate, in Vector<float> minimumDepth, in Vector<int> rawContactCount, int i, out Vector<int> exists)
        {
            //exists = Vector.BitwiseAnd(Vector.GreaterThan(candidate.Depth, minimumDepth), Vector.LessThan(new Vector<int>(i), rawContactCount));
            SetVector(ref left, i);
            exists = Vector.BitwiseAnd(Vector.GreaterThan(candidate.Depth, minimumDepth), Vector.LessThan(left, rawContactCount));
        }

        public static void Reduce(ref ManifoldCandidate candidates, in Vector<int> rawContactCount, int maxCandidateCount,
            in Vector3Wide faceNormalA, in Vector3Wide normal, in Vector3Wide faceCenterBToFaceCenterA, in Vector3Wide tangentBX, in Vector3Wide tangentBY,
            in Vector<float> epsilonScale, in Vector<float> minimumDepth, int pairCount,
            out ManifoldCandidate contact0, out ManifoldCandidate contact1, out ManifoldCandidate contact2, out ManifoldCandidate contact3,
            out Vector<int> contact0Exists, out Vector<int> contact1Exists, out Vector<int> contact2Exists, out Vector<int> contact3Exists)
        {
            contact0 = default; contact1 = default; contact2 = default; contact3 = default;
            // 看看我们能否避免访问一些较高的指数。
            // 屏蔽在实际不存在的对上生成的任何触点。它们会浪费时间并引发问题。
            Vector<int> maskedContactCount = rawContactCount;
            ref var maskedBase = ref Unsafe.As<Vector<int>, int>(ref maskedContactCount);
            for (int i = pairCount; i < Vector<int>.Count; ++i)
            {
                Unsafe.Add(ref maskedBase, i) = 0;
            }
            for (int i = maxCandidateCount; i >= 0; --i)
            {
                if (Vector.EqualsAny(maskedContactCount, new Vector<int>(i)))
                {
                    maxCandidateCount = i;
                    break;
                }
            }
            if (maxCandidateCount == 0)
            {
                contact0Exists = Vector<int>.Zero;
                contact1Exists = Vector<int>.Zero;
                contact2Exists = Vector<int>.Zero;
                contact3Exists = Vector<int>.Zero;
                return;
            }
            // 太多了,四个就够了。我们应该选择如何处理多余的。
            // 如果有任何明显的深度差异,保持最深的接触是很重要的,所以我们需要在减少之前计算深度。
            // 从概念上讲,我们沿接触法线从面B上的点向面A的平面投射光线：
            // 深度=点(pointOnfaceB-faceCenter A,faceNormalA)/点(faceNormalA,NormalA)
            // dotAxis=faceNormalA/点(faceNormalA,Normal)
            // 深度=点(pointOnFaceB-faceCenterA,dotAxis)
            // 深度=点(faceCenterB+TangentBX*Candidate.X+TangentBY*Candidate.Y-faceCenterA,dotAxis)
            // 深度=点(faceCenterB-faceCenterA,dotAxis)+点(切线BX,点轴)*候选.X+点(切线BY,点轴)*候选.Y
            Vector3Wide.Dot(faceNormalA, normal, out var axisScale);
            axisScale = Vector<float>.One / axisScale;
            Vector3Wide.Scale(faceNormalA, axisScale, out var dotAxis);
            Vector3Wide.Dot(faceCenterBToFaceCenterA, dotAxis, out var negativeBaseDot);
            Vector3Wide.Dot(tangentBX, dotAxis, out var xDot);
            Vector3Wide.Dot(tangentBY, dotAxis, out var yDot);

            for (int i = 0; i < maxCandidateCount; ++i)
            {
                ref var candidate = ref Unsafe.Add(ref candidates, i);
                candidate.Depth = candidate.X * xDot + candidate.Y * yDot - negativeBaseDot;
            }
            // 看看我们能不能因为被深度拒绝的候选人而压缩计数。
            for (int i = maxCandidateCount - 1; i >= 0; --i)
            {
                ref var candidate = ref Unsafe.Add(ref candidates, i);
                CandidateExists(candidate, minimumDepth, maskedContactCount, i, out var contactExists);
                if (Vector.EqualsAny(contactExists, new Vector<int>(-int.MaxValue)))
                {
                    maxCandidateCount = i + 1;
                    break;
                }
            }

            // 这种提早出现的现象打破了决定论。早出和非早出产生不同的结果,早出的选择取决于整个捆绑包。
            // 由于包受到来自重叠发现的不确定性的影响,这不能在启用确定性的情况下使用。
            // 目前,我们只是将其保持禁用状态,并假定碰撞批处理程序无论如何都会产生确定性的结果。
            // 实际性能影响相当小(0-2%的模拟时间)。
            // IF(maxCandidateCount<=4)
            // {
            // //不需要减少;所有车道都适合4触点歧管。
            // //这有时会导致一些非常可疑的冗余接触,但我们的ε足够小,以至于大多数这样的东西
            // //无论如何都会被放行的。
            // 联系人0=应聘者;
            // contact1=不安全。添加(参考候选项,1);
            // contact2=不安全。添加(参考候选项,2);
            // contact3=不安全。添加(参考候选人,3);
            // CandidateExists(contact0,minumDepth,maskedContactCount,0,out contact0Exists);
            // CandidateExists(contact1,minumDepth,maskedContactCount,1,out contact1Exists);
            // CandidateExists(contact2,minumDepth,maskedContactCount,2,out contact2Exists);
            // CandidateExists(contact3,minumDepth,maskedContactCount,3,out contact3Exists);
            // 返回;
            // }

            // 次要任务：实际上不需要浪费时间将其初始化为无效值。
            var bestScore = BestScore;//new Vector<float>(-float.MaxValue);
            // 虽然深度是主要的启发式方法,但在近乎相等的情况下,使用末端作为偏差,以保持初始联系人选择的一致性。
            var extremityScale = epsilonScale * 1e-2f;
            for (int i = 0; i < maxCandidateCount; ++i)
            {
                ref var candidate = ref Unsafe.Add(ref candidates, i);
                CandidateExists(candidate, minimumDepth, maskedContactCount, i, out var candidateExists);
                // 注意极限启发式。我们需要几个属性：
                // 1)在常见情况下对碰撞有一定的弹性。
                // 2)价格便宜。
                // 3)不能导致活动联系上的投机性联系。
                // 虽然有条件地使用按比例缩放的abs(Candidate.X)在2和3上运行良好,但许多用例仅在x轴上就会产生联系。
                // X和Y加在一起稍好一些,但45度角并不少见,可能会导致相同的问题。
                // 所以我们只使用任意方向的点积。
                // 这是一个相当小的细节,但它足够便宜,没有理由不利用它。
                var extremity = Vector.Abs(candidate.X * 0.7946897654f + candidate.Y * 0.60701579614f);
                var candidateScore = candidate.Depth + Vector.ConditionalSelect(Vector.GreaterThanOrEqual(candidate.Depth, Vector<float>.Zero), extremity * extremityScale, Vector<float>.Zero);
                var candidateIsHighestScore = Vector.BitwiseAnd(candidateExists, Vector.GreaterThan(candidateScore, bestScore));
                ConditionalSelect(candidateIsHighestScore, candidate, contact0, out contact0);
                bestScore = Vector.ConditionalSelect(candidateIsHighestScore, candidateScore, bestScore);
            }
            contact0Exists = Vector.GreaterThan(bestScore, BestScore/*new Vector<float>(-float.MaxValue)*/);

            // 查找距离起始触点最远的点。
            var maxDistanceSquared = Vector<float>.Zero;
            for (int i = 0; i < maxCandidateCount; ++i)
            {
                ref var candidate = ref Unsafe.Add(ref candidates, i);
                var offsetX = candidate.X - contact0.X;
                var offsetY = candidate.Y - contact0.Y;
                var distanceSquared = offsetX * offsetX + offsetY * offsetY;
                //// 惩罚投机性接触;它们通常不那么重要。
                // 距离平方=Vector.ConditionalSelect(Vector.LessThan(candidate.Depth,矢量<浮点>.零),0.125f*距离平方,距离平方);
                CandidateExists(candidate, minimumDepth, maskedContactCount, i, out var candidateExists);
                var candidateIsMostDistant = Vector.BitwiseAnd(Vector.GreaterThan(distanceSquared, maxDistanceSquared), candidateExists);
                maxDistanceSquared = Vector.ConditionalSelect(candidateIsMostDistant, distanceSquared, maxDistanceSquared);
                ConditionalSelect(candidateIsMostDistant, candidate, contact1, out contact1);
            }
            // 如果第一个和第二个候选人之间的距离是零,那么增加联系就没有意义了。请注意,这将捕获存在0个或1个联系人的情况。
            contact1Exists = Vector.GreaterThan(maxDistanceSquared, epsilonScale * epsilonScale * Epsilon);//new Vector<float>(1e-6f));

            // 现在再找出两点。使用现有的两个触点作为起始边,拾取当被视为带边的三角形时,
            // 有最大震级的负号和正号区域。
            var edgeOffsetX = contact1.X - contact0.X;
            var edgeOffsetY = contact1.Y - contact0.Y;
            var minSignedArea = Vector<float>.Zero;
            var maxSignedArea = Vector<float>.Zero;
            for (int i = 0; i < maxCandidateCount; ++i)
            {
                // 三角形的面积与其两个边偏移的叉积的大小成正比。
                // 为了保留符号,我们在概念上会在脸部的法线上打点。因为我们是在B面的表面上工作,所以算术大大简化为perp-dot乘积。
                ref var candidate = ref Unsafe.Add(ref candidates, i);
                var candidateOffsetX = candidate.X - contact0.X;
                var candidateOffsetY = candidate.Y - contact0.Y;
                var signedArea = candidateOffsetX * edgeOffsetY - candidateOffsetY * edgeOffsetX;
                // 惩罚投机性接触;它们通常不那么重要。
                signedArea = Vector.ConditionalSelect(Vector.LessThan(candidate.Depth, Vector<float>.Zero), 0.25f * signedArea, signedArea);

                CandidateExists(candidate, minimumDepth, maskedContactCount, i, out var candidateExists);
                var isMinArea = Vector.BitwiseAnd(Vector.LessThan(signedArea, minSignedArea), candidateExists);
                minSignedArea = Vector.ConditionalSelect(isMinArea, signedArea, minSignedArea);
                ConditionalSelect(isMinArea, candidate, contact2, out contact2);
                var isMaxArea = Vector.BitwiseAnd(Vector.GreaterThan(signedArea, maxSignedArea), candidateExists);
                maxSignedArea = Vector.ConditionalSelect(isMaxArea, signedArea, maxSignedArea);
                ConditionalSelect(isMaxArea, candidate, contact3, out contact3);
            }

            // 面积可以近似为用于ε测试的方框：(edgeLength0*span)^2=(edgeLength0*edgeLength0*span*span)。
            // 此比较基本上是检查"span"是否过小,因此我们可以将其更改为：
            // edgeLength0*edgeLength0*(edgeLength0*tinyNumber)*(edgeLength0*tinyNumber)=(edgeLength0^2)*(edglength0^2)*(tinyNumber^2)
            var epsilon = maxDistanceSquared * maxDistanceSquared * Epsilon;// new Vector<float>(1e-6f);
            // 请注意,构造保证minSignedArea为零或更低,因此可以安全地平方进行幅度比较。
            // 请注意,这些epsilon捕获有两个或更少原始触点的情况。
            contact2Exists = Vector.GreaterThan(minSignedArea * minSignedArea, epsilon);
            contact3Exists = Vector.GreaterThan(maxSignedArea * maxSignedArea, epsilon);
        }

        static Vector<float> Epsilon = new Vector<float>(1e-6f);
        static Vector<float> BestScore = new Vector<float>(-float.MaxValue);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        unsafe static void PlaceCandidateInSlot(in ManifoldCandidateScalar candidate, int contactIndex,
            in Vector3 faceCenterB, in Vector3 faceBX, in Vector3 faceBY, float depth,
            in Matrix3x3 orientationB, in Vector3 offsetB, ref Convex4ContactManifoldWide manifoldSlot)
        {
            var localPosition = candidate.X * faceBX + candidate.Y * faceBY + faceCenterB;
            Matrix3x3.Transform(localPosition, orientationB, out var position);
            position += offsetB;
            Vector3Wide.WriteFirst(position, ref Unsafe.Add(ref manifoldSlot.OffsetA0, contactIndex));
            GetFirst(ref Unsafe.Add(ref manifoldSlot.Depth0, contactIndex)) = depth;
            GetFirst(ref Unsafe.Add(ref manifoldSlot.FeatureId0, contactIndex)) = candidate.FeatureId;
            GetFirst(ref Unsafe.Add(ref manifoldSlot.Contact0Exists, contactIndex)) = -1;

        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static unsafe void RemoveCandidateAt(ManifoldCandidateScalar* candidates, float* depths, int removalIndex, ref int candidateCount)
        {
            var lastIndex = candidateCount - 1;
            if (removalIndex < lastIndex)
            {
                candidates[removalIndex] = candidates[lastIndex];
                depths[removalIndex] = depths[lastIndex];
            }
            --candidateCount;
        }

        public unsafe static void Reduce(ManifoldCandidateScalar* candidates, int candidateCount,
            in Vector3 faceNormalA, in Vector3 localNormal, in Vector3 faceCenterA, in Vector3 faceCenterB, in Vector3 tangentBX, in Vector3 tangentBY,
            float epsilonScale, float minimumDepth, in Matrix3x3 rotationToWorld, in Vector3 worldOffsetB, int slotIndex, ref Convex4ContactManifoldWide manifoldWide)
        {
            if (candidateCount == 0)
            {
                // 没有联系人,就没有工作。请注意,我们假设用户已将所有插槽的所有ContactExist设置为0。
                return;
            }
            // 请注意,这不会指定流形中的世界法线。
            ref var manifoldSlot = ref GetOffsetInstance(ref manifoldWide, slotIndex);

            // 计算所有候选人的深度。
            // 如果有任何明显的深度差异,保持最深的接触是很重要的,所以我们需要在减少之前计算深度。
            // 从概念上讲,我们沿接触法线从面B上的点向面A的平面投射光线：
            // 深度=点(pointOnfaceB-faceCenter A,faceNormalA)/点(faceNormalA,NormalA)
            // dotAxis=faceNormalA/点(faceNormalA,Normal)
            // 深度=点(pointOnFaceB-faceCenterA,dotAxis)
            // 深度=点(faceCenterB+TangentBX*Candidate.X+TangentBY*Candidate.Y-faceCenterA,dotAxis)
            // 深度=点(faceCenterB-faceCenterA,dotAxis)+点(切线BX,点轴)*候选.X+点(切线BY,点轴)*候选.Y
            var dotAxis = faceNormalA / Vector3.Dot(faceNormalA, localNormal);
            var faceCenterAToFaceCenterB = faceCenterB - faceCenterA;
            var baseDot = Vector3.Dot(faceCenterAToFaceCenterB, dotAxis);
            var xDot = Vector3.Dot(tangentBX, dotAxis);
            var yDot = Vector3.Dot(tangentBY, dotAxis);
            var candidateDepths = stackalloc float[candidateCount];
            for (int i = candidateCount - 1; i >= 0; --i)
            {
                ref var candidate = ref candidates[i];
                ref var candidateDepth = ref candidateDepths[i];
                candidateDepth = baseDot + candidate.X * xDot + candidate.Y * yDot;
                // 修剪深度阈值以下的接触。
                if (candidateDepth < minimumDepth)
                {
                    RemoveCandidateAt(candidates, candidateDepths, i, ref candidateCount);
                }
            }
            if (candidateCount <= 4)
            {
                // 不需要减少;只需将触点放入歧管即可。
                for (int i = 0; i < candidateCount; ++i)
                {
                    PlaceCandidateInSlot(candidates[i], i, faceCenterB, tangentBX, tangentBY, candidateDepths[i], rotationToWorld, worldOffsetB, ref manifoldSlot);
                }
                return;
            }

            // 次要任务：实际上不需要浪费时间将其初始化为无效值。
            var bestScore0 = float.MinValue;
            var bestIndex0 = 0;
            // 虽然深度是主要的启发式方法,但在近乎相等的情况下,使用末端作为偏差,以保持初始联系人选择的一致性。
            var extremityScale = epsilonScale * 1e-2f;
            var extremityX = 0.7946897654f * extremityScale;
            var extremityY = 0.60701579614f * extremityScale;
            for (int i = 0; i < candidateCount; ++i)
            {
                ref var candidate = ref candidates[i];
                ref var candidateDepth = ref candidateDepths[i];
                float candidateScore = candidateDepth;
                if (candidateDepth >= 0)
                {
                    // 注意极限启发式。我们需要几个属性：
                    // 1)在常见情况下对碰撞有一定的弹性。
                    // 2)价格便宜。
                    // 3)不能导致活动联系上的投机性联系。
                    // 虽然有条件地使用按比例缩放的abs(Candidate.X)在2和3上运行良好,但许多用例仅在x轴上就会产生联系。
                    // X和Y加在一起稍好一些,但45度角并不少见,可能会导致相同的问题。
                    // 所以我们只使用任意方向的点积。
                    // 这是一个相当小的细节,但它足够便宜,没有理由不利用它。
                    var extremity = candidate.X * extremityX + candidate.Y * extremityY;
                    if (extremity < 0)
                        extremity = -extremity;
                    candidateScore += extremity;
                }
                if (candidateScore > bestScore0)
                {
                    bestScore0 = candidateScore;
                    bestIndex0 = i;
                }
            }
            var candidate0 = candidates[bestIndex0];
            var depth0 = candidateDepths[bestIndex0];
            PlaceCandidateInSlot(candidate0, 0, faceCenterB, tangentBX, tangentBY, depth0, rotationToWorld, worldOffsetB, ref manifoldSlot);
            RemoveCandidateAt(candidates, candidateDepths, bestIndex0, ref candidateCount);

            // 查找距离起始触点最远的点。
            var maximumDistanceSquared = -1f;
            var bestIndex1 = 0;
            for (int i = 0; i < candidateCount; ++i)
            {
                ref var candidate = ref candidates[i];
                var offsetX = candidate.X - candidate0.X;
                var offsetY = candidate.Y - candidate0.Y;
                var distanceSquared = offsetX * offsetX + offsetY * offsetY;
                //// 惩罚投机性接触;它们通常不那么重要。
                // DistanceSquared=候选深度[i]<0？0.125f*DistanceSquared：DistanceSquared;
                if (distanceSquared > maximumDistanceSquared)
                {
                    maximumDistanceSquared = distanceSquared;
                    bestIndex1 = i;
                }
            }
            if (maximumDistanceSquared < 1e-6f * epsilonScale * epsilonScale)
            {
                // 如果第一个和第二个候选人之间的距离是零,那么增加联系就没有意义了。
                return;
            }
            var candidate1 = candidates[bestIndex1];
            var depth1 = candidateDepths[bestIndex1];
            PlaceCandidateInSlot(candidate1, 1, faceCenterB, tangentBX, tangentBY, depth1, rotationToWorld, worldOffsetB, ref manifoldSlot);
            RemoveCandidateAt(candidates, candidateDepths, bestIndex1, ref candidateCount);


            // 现在再找出两点。使用现有的两个触点作为起始边,拾取当被视为带边的三角形时,
            // 有最大震级的负号和正号区域。
            var edgeOffsetX = candidate1.X - candidate0.X;
            var edgeOffsetY = candidate1.Y - candidate0.Y;
            var minSignedArea = 0f;
            var maxSignedArea = 0f;
            var bestIndex2 = 0;
            var bestIndex3 = 0;
            for (int i = 0; i < candidateCount; ++i)
            {
                // IF(i==Best Index0||i==Best Index1)
                // 继续;
                // 三角形的面积与其两个边偏移的叉积的大小成正比。
                // 为了保留符号,我们在概念上会在脸部的法线上打点。因为我们是在B面的表面上工作,所以算术大大简化为perp-dot乘积。
                ref var candidate = ref candidates[i];
                var candidateOffsetX = candidate.X - candidate0.X;
                var candidateOffsetY = candidate.Y - candidate0.Y;
                var signedArea = candidateOffsetX * edgeOffsetY - candidateOffsetY * edgeOffsetX;
                // 惩罚投机性接触;它们通常不那么重要。
                if (candidateDepths[i] < 0)
                    signedArea *= 0.25f;

                if (signedArea < minSignedArea)
                {
                    minSignedArea = signedArea;
                    bestIndex2 = i;
                }
                if (signedArea > maxSignedArea)
                {
                    maxSignedArea = signedArea;
                    bestIndex3 = i;
                }
            }

            // 面积可以近似为用于ε测试的方框：(edgeLength0*span)^2=(edgeLength0*edgeLength0*span*span)。
            // 此比较基本上是检查"span"是否过小,因此我们可以将其更改为：
            // edgeLength0*edgeLength0*(edgeLength0*tinyNumber)*(edgeLength0*tinyNumber)=(edgeLength0^2)*(edglength0^2)*(tinyNumber^2)
            var areaEpsilon = maximumDistanceSquared * maximumDistanceSquared * 1e-6f;
            if (minSignedArea * minSignedArea > areaEpsilon)
            {
                PlaceCandidateInSlot(candidates[bestIndex2], 2, faceCenterB, tangentBX, tangentBY, candidateDepths[bestIndex2], rotationToWorld, worldOffsetB, ref manifoldSlot);
            }
            if (maxSignedArea * maxSignedArea > areaEpsilon)
            {
                PlaceCandidateInSlot(candidates[bestIndex3], 3, faceCenterB, tangentBX, tangentBY, candidateDepths[bestIndex3], rotationToWorld, worldOffsetB, ref manifoldSlot);
            }
        }
    }
}
